package com.gitee.apanlh.util.algorithm.encrypt.symmetric;

import com.gitee.apanlh.exp.algorithm.PaddingException;
import com.gitee.apanlh.exp.algorithm.SymmetricException;
import com.gitee.apanlh.exp.algorithm.UnPaddingException;
import com.gitee.apanlh.util.algorithm.KeyUtils;
import com.gitee.apanlh.util.algorithm.encrypt.AlgorithmMode;
import com.gitee.apanlh.util.algorithm.encrypt.AlgorithmPadding;
import com.gitee.apanlh.util.algorithm.encrypt.CipherMode;
import com.gitee.apanlh.util.base.ChooseOr;
import com.gitee.apanlh.util.base.Empty;
import com.gitee.apanlh.util.base.Eq;
import com.gitee.apanlh.util.base.StringUtils;
import com.gitee.apanlh.util.check.CheckImport;
import com.gitee.apanlh.util.check.CheckLibrary;
import com.gitee.apanlh.util.encode.Base64Utils;
import com.gitee.apanlh.util.encode.HexUtils;
import com.gitee.apanlh.util.reflection.ClassConvertUtils;
import com.gitee.apanlh.util.valid.Assert;
import com.gitee.apanlh.util.valid.ValidParam;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.Provider;
import java.security.Security;
import java.security.spec.AlgorithmParameterSpec;

/**
 * 	对称算法加密/解密抽象类
 * 	<br>默认实现了加密和解密功能
 * 	<br>该抽象类提供了对称加密算法的通用操作和方法，子类可以继承此类并根据具体的对称加密算法实现相应的加密和解密逻辑。
 * 	<br>该抽象类依赖于 Bouncy Castle提供的加密算法库，需要在使用前添加 Bouncy Castle作为加密算法提供者
 * 	<br>注意：该类的部分方法参数或字段可能需要根据具体的对称加密算法进行调整和适配
 * 	<br>参考文档：Bouncy Castle:
 * 	<a href="https://www.bouncycastle.org/">https://www.bouncycastle.org/</a>
 *  <p>
 * 	#mark 需要增加密钥管理功能，增加流式传输过程（加密文件传输、数据备份加密、加密存储介质、加密多媒体内容、加密数据库备份、加密日志文件），增加对大文件流式处理
 *  @author Pan
 */
public abstract class BouncyCastleSymmetricAbstract extends SymmetricAbstract implements SymmetricEncode {
	
	/** 静态Provider */
	static Provider bouncyCastleProvider;
	
	static {
		CheckImport.library(CheckLibrary.BOUNCY_CASTLE);
		bouncyCastleProvider = new BouncyCastleProvider();
		Security.addProvider(bouncyCastleProvider);
	}
	
	/** 算法类型 */
	private SymmetricType symmetricType;
	/** 算法名称 */
	private String algorithm;
	/** 加密器 */
	private Cipher encryptCipher;
	/** 解密器 */
	private Cipher decryptCipher;
	/** 加密器模式 */
	private CipherMode cipherMode;
	/** 加解密分组模式 */
	private AlgorithmMode algorithmMode;
	/** 加解密填充模式 */
	private AlgorithmPadding algorithmPadding;
	/** 密钥KEY */
	private SecretKey secretKey;
	/** 加密解密参数 */
	private AlgorithmParameterSpec algorithmParameterSpec;
	/** 密钥 */
	private byte[] key;
	/** 向量 */
	private byte[] iv;
	/** PBE算法 */
	private PBEAlgorithms pbeAlgorithms;
	/** 盐值 */
	private byte[] salt;
	/** 迭代值 */
	private int iterationCount;
	
	
	/**	
	 * 	构造函数-初始化加解密器
	 * 	<br>自定义算法类型，构建时默认算法自带模式比如AES=AES/ECB/PKCS5Padding，DES= DES/ECB/PKCS5Padding
	 * 	<br>动态加载加解密器模式（加密模式、解密模式）
	 * 	<br>默认随机生成128位密钥长度
	 * 	
	 * 	@author Pan
	 * 	@param 	symmetricType	对称加密算法类型
	 */
	protected BouncyCastleSymmetricAbstract(SymmetricType symmetricType) {
		this(symmetricType, AlgorithmMode.ECB, AlgorithmPadding.PKCS5);
	}
	
	/**	
	 * 	构造函数-初始化加解密器
	 * 	<br>默认随机生成128位密钥长度，如果为CBC等带IV的模式，则自动生成IV(128位)
	 * 	<br>自定义算法类型，构建时默认算法自带模式比如AES(AES/ECB/PKCS5Padding)，DES(DES/ECB/PKCS5Padding)
	 * 	<br>动态加载加解密器模式（加密模式、解密模式）
	 * 	<br>自定义算法模式(ECB、CBC等)
	 * 	<br>自定义算法填充模式(PKCS1、PKCS5等)
	 * 
	 * 	@author Pan
	 * 	@param 	symmetricType		对称加密算法类型
	 * 	@param 	algorithmMode		算法模式
	 * 	@param 	algorithmPadding	算法填充
	 */
	protected BouncyCastleSymmetricAbstract(SymmetricType symmetricType, AlgorithmMode algorithmMode, AlgorithmPadding algorithmPadding) {
		this(KeyUtils.generate128(), null, symmetricType, algorithmMode, algorithmPadding);
	}
	
	/**	
	 * 	构造函数-初始化加解密器
	 * 	<br>自定义算法类型，构建时默认算法自带模式比如AES=AES/ECB/PKCS5Padding，DES= DES/ECB/PKCS5Padding
	 * 	<br>自定义密钥
	 * 	<br>自定义向量
	 * 	<br>动态加载加解密器模式（加密模式、解密模式）
	 * 	
	 * 	@author Pan
	 * 	@param 	key	  			密钥
	 * 	@param 	iv	  			向量
	 * 	@param 	symmetricType	对称加密算法类型
	 */
	protected BouncyCastleSymmetricAbstract(byte[] key, byte[] iv, SymmetricType symmetricType) {
		this(key, iv, symmetricType, AlgorithmMode.ECB, AlgorithmPadding.PKCS5);
	}
	
	/**	
	 * 	构造函数-初始化加解密器
	 * 	<br>自定义算法类型，构建时默认算法自带模式比如AES=AES/ECB/PKCS5Padding，DES= DES/ECB/PKCS5Padding
	 * 	<br>自定义密钥
	 * 	<br>自定义向量
	 * 	<br>动态加载加解密器模式（加密模式、解密模式）
	 * 	<br>自定义算法模式(ECB、CBC等)
	 * 	<br>自定义算法填充模式(PKCS1、PKCS5等)
	 * 
	 * 	@author Pan
	 * 	@param 	key	  				密钥
	 * 	@param 	iv	  				向量
	 * 	@param 	symmetricType		对称加密算法类型
	 * 	@param 	algorithmMode		算法模式
	 * 	@param 	algorithmPadding	算法填充
	 */
	protected BouncyCastleSymmetricAbstract(byte[] key, byte[] iv, SymmetricType symmetricType, AlgorithmMode algorithmMode, AlgorithmPadding algorithmPadding) {
		Assert.isNotEmpty(key);
		
		this.iv = iv;
		this.key = key;
		this.symmetricType = symmetricType;
		this.algorithmMode = algorithmMode;
		this.algorithmPadding = algorithmPadding;
	}
	
	/**	
	 * 	构造函数-初始化PEB算法加解密器
	 * 	<br>自定义key
	 * 	<br>自定义盐值
	 * 	<br>自定义迭代次数
	 * 	<br>自定义PEB算法类型
	 * 	<br>动态加载加解密器模式（加密模式、解密模式）
	 * 	
	 * 	@author Pan
	 * 	@param 	key				密钥
	 * 	@param 	salt			盐值
	 * 	@param 	iterationCount	迭代次数
	 * 	@param 	pbeAlgorithms	对称加密算法类型
	 */
	protected BouncyCastleSymmetricAbstract(byte[] key, byte[] salt, int iterationCount, PBEAlgorithms pbeAlgorithms) {
		Assert.isNotEmpty(key);
		Assert.isNotEmpty(salt);
		Assert.isTrue(iterationCount > 0);
		
		this.symmetricType = SymmetricType.PBE;
		this.key = key;
		this.salt = salt;
		this.iterationCount = iterationCount;
		this.pbeAlgorithms = pbeAlgorithms;
	}
	
	@Override
	public Cipher initCipher(byte[] key, byte[] iv, CipherMode cipherMode) {
		return initCipher(key, iv, cipherMode, null, null);
	}
	
	@Override
	public Cipher initCipher(String key, byte[] iv, CipherMode cipherMode) {
		Assert.isNotEmpty(key);
		return initCipher(key.getBytes(), iv, cipherMode);
	}
	
	@Override
	public Cipher initCipher(byte[] key, byte[] iv, CipherMode cipherMode, AlgorithmMode algorithmMode, AlgorithmPadding algorithmPadding) {
		this.algorithm = ChooseOr.create(ValidParam.isNotNull(algorithmMode, algorithmPadding), () -> StringUtils.format("{}/{}/{}", this.symmetricType.getAlgorithm(), algorithmMode.getMode(), algorithmPadding.getMode()))
			.orElse(this.symmetricType.getAlgorithm());
		
		try {
			Cipher cipher = Cipher.getInstance(this.algorithm, getProvider());
			// 根据加密算法确定长度
			if (this.secretKey == null) {
				this.secretKey = new SecretKeySpec(key, this.algorithm);
			}
			cipher.init(cipherMode.getMode(), this.secretKey, initParameter());
			return setCipher(cipherMode, cipher);
		} catch (Exception e) {
			throw new SymmetricException("load algorithm:{}, {}", e, this.algorithm, e.getMessage());
		}
	}
	
	@Override
	public Cipher initCipher(String key, byte[] iv, CipherMode cipherMode, AlgorithmMode algorithmMode, AlgorithmPadding algorithmPadding) {
		Assert.isNotEmpty(key);
		return initCipher(key.getBytes(), iv, cipherMode, algorithmMode, algorithmPadding);
	}
	
	/**	
	 * 	初始化PBE算法
	 * 	<br>参数已经在构造函数初始化完毕
	 * 	
	 * 	@author Pan
	 * 	@param	cipherMode	加密器模式
	 * 	@return	Cipher
	 */
	public Cipher initPBECipher(CipherMode cipherMode) {
		this.algorithm = this.pbeAlgorithms.getAlgorithm();
		
		try {
			Cipher cipher = Cipher.getInstance(this.algorithm, getProvider());
			// 根据加密算法确定长度
			if (this.secretKey == null) {
				PBEKeySpec pbeKeySpec = new PBEKeySpec(ClassConvertUtils.toChar(this.key), this.salt, this.iterationCount);
				SecretKeyFactory instance = SecretKeyFactory.getInstance(this.algorithm, getProvider());
				this.secretKey = instance.generateSecret(pbeKeySpec);
			}
			cipher.init(cipherMode.getMode(), this.secretKey, initParameter());
			return setCipher(cipherMode, cipher);
		} catch (Exception e) {
			throw new SymmetricException("PBE load error algorithm:{}, {}", e, this.algorithm, e.getMessage());
		}
	}
	
	/**
	 * 	初始化参数包含向量或盐值
	 * 	<br>如果特殊模式比如CBC向量为null则会随机向量(128位长度)
	 * 		
	 * 	@author Pan
	 * 	@return	AlgorithmParameterSpec
	 */
	public AlgorithmParameterSpec initParameter() {
		if (ValidParam.isNotNull(this.algorithmParameterSpec)) {
			return this.algorithmParameterSpec;
		}
		
		if (Eq.enumsOr(this.symmetricType, SymmetricType.AES, SymmetricType.DES, SymmetricType.DESEDE, SymmetricType.SM4)
				&& Eq.enumsOr(this.algorithmMode, AlgorithmMode.CBC, AlgorithmMode.CFB, AlgorithmMode.OFB, AlgorithmMode.CTR)) {
			//	默认随机128位
			if (this.iv == null) {
				this.iv = KeyUtils.generate128();
			}
			//	初始化向量
			this.algorithmParameterSpec = new IvParameterSpec(this.iv);
		}
		
		if (Eq.enums(SymmetricType.PBE, this.symmetricType)) {
			//	默认随机128位盐值
			if (this.salt == null) {
				this.salt = KeyUtils.generate128();
			}
			//	默认100次
			if (this.iterationCount <= 0) {
				this.iterationCount = 100;
			}
			this.algorithmParameterSpec = new PBEParameterSpec(this.salt, this.iterationCount);
		}
		return this.algorithmParameterSpec;
	}

	@Override
	public byte[] doFinal(CipherMode cipherMode, byte[] content) {
		return doFinal(cipherMode, content, BLOCK_SIZE);
	}
	
	@Override
	public byte[] doFinal(CipherMode cipherMode, byte[] content, int blockSize) {
		Cipher cipher = getCipher(cipherMode);
		
		try {
			if (Eq.enums(CipherMode.ENCRYPT, cipherMode)) {
				//	默认选择ZERO才会进行填充
				byte[] doPadding = doPadding(content, blockSize);
				return cipher.doFinal(doPadding);
			} 
			//	默认选择ZERO才会进行去除填充
			byte[] paddedContent = cipher.doFinal(content);
			return doUnpadding(paddedContent, blockSize);
		} catch (Exception e) {
			throw new SymmetricException(e.getMessage(), e);
		}
	}
	
	@Override
	public byte[] doPadding(byte[] content, int blockSize) {
		try {
			if (Eq.enums(AlgorithmPadding.ZERO, this.algorithmPadding)) {
				return super.zeroPadding(CipherMode.ENCRYPT, content, blockSize);
			}
			return content;
		} catch (Exception e) {
			throw new PaddingException(e.getMessage());
		}
	}
	
	@Override
	public byte[] doUnpadding(byte[] paddedContent, int blockSize) {
		try {
			if (Eq.enums(AlgorithmPadding.ZERO, this.algorithmPadding)) {
				return super.zeroPadding(CipherMode.DECRYPT, paddedContent, blockSize);
			}
			return paddedContent;
		} catch (Exception e) {
			throw new UnPaddingException(e.getMessage());
		}
	}

	/**
	 * 	设置加解密器
	 * 	
	 * 	@author Pan
	 * 	@param 	cipherMode	加解密器模式
	 * 	@param 	cipher		加解密器
	 * 	@return Cipher
	 */
	private Cipher setCipher(CipherMode cipherMode, Cipher cipher) {
		if (Eq.enums(CipherMode.ENCRYPT, cipherMode)) {
			this.encryptCipher = cipher;
		} else {
			this.decryptCipher = cipher;
		}
		return cipher;
	}
	
	/**	
	 * 	选择获取加解密器
	 *  TODO 这里和setCipher可能异曲同工之妙,可以修改一下
	 *
	 * 	@author Pan
	 * 	@param 	cipherMode 加密器模式
	 * 	@return	Cipher
	 */
	private Cipher getCipher(CipherMode cipherMode) {
		if (Eq.enums(CipherMode.ENCRYPT, cipherMode)) {
			if (this.encryptCipher == null) {
				if (Eq.enums(SymmetricType.PBE, this.symmetricType)) {
					this.encryptCipher = initPBECipher(cipherMode);
					return getEncryCipher();
				}
				//	TODO 补充
				if (Eq.enums(SymmetricType.DESEDE, this.symmetricType)) {
					this.encryptCipher = initCipher(this.key, this.iv, cipherMode, this.algorithmMode, this.algorithmPadding);
					return getEncryCipher();
				}
				if (Eq.enums(SymmetricType.DES, this.symmetricType)) {
					this.encryptCipher = initCipher(this.key, this.iv, cipherMode, this.algorithmMode, this.algorithmPadding);
					return getEncryCipher();
				}
				this.encryptCipher = initCipher(this.key, this.iv, cipherMode, this.algorithmMode, this.algorithmPadding);
			}
			return getEncryCipher();
		}

		if (this.decryptCipher == null) {
			if (Eq.enums(SymmetricType.PBE, this.symmetricType)) {
				this.decryptCipher = initPBECipher(cipherMode);
				return getDecryptCipher();
			}
			if (Eq.enums(SymmetricType.DESEDE, this.symmetricType)) {
				this.decryptCipher = initCipher(this.key, this.iv, cipherMode, this.algorithmMode, this.algorithmPadding);
				return getDecryptCipher();
			}
			if (Eq.enums(SymmetricType.DES, this.symmetricType)) {
				this.decryptCipher = initCipher(this.key, this.iv, cipherMode, this.algorithmMode, this.algorithmPadding);
				return getDecryptCipher();
			}
			this.decryptCipher = initCipher(this.key, this.iv, cipherMode, this.algorithmMode, this.algorithmPadding);
		}
		return getDecryptCipher();
	}
	
	/**	
	 * 	获取加密器模式
	 * 
	 * 	@author Pan
	 * 	@return	CipherMode
	 */
	public CipherMode getCipherMode() {
		return cipherMode;
	}

	/**	
	 * 	获取	加解密分组模式
	 * 
	 * 	@author Pan
	 * 	@return	AlgorithmMode
	 */
	public AlgorithmMode getAlgorithmMode() {
		return algorithmMode;
	}

	/**	
	 * 	获取加密/解密填充模式
	 * 
	 * 	@author Pan
	 * 	@return	AlgorithmPadding
	 */
	public AlgorithmPadding getAlgorithmPadding() {
		return algorithmPadding;
	}

	/**
	 * 	获取加密器
	 * 
	 * 	@author Pan
	 * 	@return	Cipher
	 */
	public Cipher getEncryCipher() {
		return this.encryptCipher;
	}
	
	/**
	 * 	获取解密器
	 * 
	 * 	@author Pan
	 * 	@return	Cipher
	 */
	public Cipher getDecryptCipher() {
		return this.decryptCipher;
	}
	
	/**	
	 * 	获取密钥
	 * 	
	 * 	@author Pan
	 * 	@return	SecretKey
	 */
	public SecretKey getSecretKey() {
		return this.secretKey;
	}
	
	/**
	 * 	获取算法名称
	 * 	
	 * 	@author Pan
	 * 	@return	String
	 */
	public String getAlgorithm() {
		return this.algorithm;
	}
	
	/**	
	 * 	获取盐值
	 * 	
	 * 	@author Pan
	 * 	@return	byte[]
	 */
	public byte[] getSalt() {
		return salt;
	}

	/**	
	 * 	获取迭代次数
	 * 	<br>PBE使用
	 * 	
	 * 	@author Pan
	 * 	@return	iterationCount
	 */
	public int getIterationCount() {
		return iterationCount;
	}
	
	@Override
	public Provider getProvider() {
		return BouncyCastleSymmetricAbstract.bouncyCastleProvider;
	}
	
	@Override
	public byte[] getIv() {
		return this.iv;
	}
	
	@Override
	public byte[] getIvToBase64() {
		return getIv() == null ? Empty.arrayByte() : Base64Utils.encode(getIv());
	}
	
	@Override
	public String getIvToBase64Str() {
		return getIv() == null ? null : Base64Utils.encodeToStr(getIv());
	}
	
	@Override
	public String getIvToHex() {
		return getIv() == null ? null : HexUtils.encode(getIv());
	}
	
	@Override
	public byte[] getKey() {
		return this.key;
	}
	
	@Override
	public byte[] getKeyToBase64() {
		return getKey() == null ? Empty.arrayByte() : Base64Utils.encode(getKey());
	}
	
	@Override
	public String getKeyToBase64Str() {
		return getKey() == null ? null : Base64Utils.encodeToStr(getKey());
	}
	
	@Override
	public String getKeyToHex() {
		return getKey() == null ? null : HexUtils.encode(getKey());
	}
	
	@Override
	public byte[] getPassword() {
		return Eq.enums(SymmetricType.PBE, this.symmetricType) ? this.key : null;
	}
	
	@Override
	public byte[] getPasswordToBase64() {
		return getPassword() == null ? Empty.arrayByte() : Base64Utils.encode(getPassword());
	}
	
	@Override
	public String getPasswordToBase64Str() {
		return getPassword() == null ? null : Base64Utils.encodeToStr(getPassword());
	}
	
	@Override
	public String getPasswordToHex() {
		return getPassword() == null ? null : HexUtils.encode(getPassword());
	}
	
	/**	
	 * 	设置盐值
	 * 	
	 * 	@author Pan
	 * 	@param  salt 盐值
	 */
	public void setSalt(byte[] salt) {
		this.salt = salt;
	}
	
	/**	
	 * 	设置迭代次数
	 * 	
	 * 	@author Pan
	 * 	@param  iterationCount 迭代次数
	 */
	public void setIterationCount(int iterationCount) {
		this.iterationCount = iterationCount;
	}
}
